//go:build !notest

package parser

import (
	"bufio"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"unsafe"

	"gitee.com/u-language/u-language/ucom/ast"
	"gitee.com/u-language/u-language/ucom/check"
	"gitee.com/u-language/u-language/ucom/errcode"
	"gitee.com/u-language/u-language/ucom/internal/errutil"
	"gitee.com/u-language/u-language/ucom/internal/time"
	"gitee.com/u-language/u-language/ucom/lex"
)

// cacheOsFile 为测试缓存需要系统调用的内容
type cacheOsFile struct {
	fd   *os.File
	buf  []byte
	size int64
}

// 仅测试使用
var (
	testmaplock sync.RWMutex                                             //同步map
	testmap     map[string]cacheOsFile = make(map[string]cacheOsFile, 4) //缓存测试时的系统调用的内容
	strbufpool                         = sync.Pool{                      //缓存字符串读取
		New: func() interface{} {
			return strings.NewReader("")
		},
	}
	bufreadpool = sync.Pool{
		New: func() interface{} {
			return bufio.NewReader(nil)
		},
	}
)

// openfileAndInfo 同时获取文件描述符和文件大小
func openfileAndInfo(path string) (*os.File, int64, []byte, error) {
	if test { //在测试
		testmaplock.RLock()             //读取加读锁
		if v, ok := testmap[path]; ok { //有缓存
			testmaplock.RUnlock()           //释放读锁
			return v.fd, v.size, v.buf, nil //返回缓存值
		}
		//写入加写锁
		testmaplock.RUnlock() //持有的读锁释放
		testmaplock.Lock()
		fd, Info, err := openfileAndStat(path)
		if err != nil {
			return nil, 0, nil, err
		}
		buf, err := os.ReadFile(path) //获取全部内容
		if err != nil {
			return nil, 0, nil, err
		}
		testmap[path] = cacheOsFile{fd: fd, size: Info.Size(), buf: buf} //缓存
		testmaplock.Unlock()                                             //释放锁
		return fd, Info.Size(), buf, err                                 //返回缓存内容
	}
	//非测试
	file, Info, err := openfileAndStat(path)
	if err != nil {
		return nil, 0, nil, err
	}
	return file, Info.Size(), nil, err
}

func openfileAndStat(path string) (*os.File, os.FileInfo, error) {
	// 打开文件
	file, err := os.Open(path)
	if err != nil {
		return nil, nil, err
	}
	info, err := file.Stat()
	if err != nil {
		return nil, nil, err
	}
	return file, info, err

}

// ParserFile 从一个代码文件创建ast树
//   - path是文件路径
//   - errctx是错误处理上下文
//   - IsCheck是控制是否进行语义检查
//   - Thread是控制是否并发
func ParserFile(path string, errctx *errcode.ErrCtx, IsCheck bool, ImportPackage *map[string]*ast.Package, Thread bool) (*ast.Tree, error) {
	file, size, buf, err := openfileAndInfo(path) //获取文件描述符，文件大小，测试时还有全部内容
	if err != nil {
		return nil, err
	}
	var r *bufio.Reader
	if test { //测试时，这些是为了在Benchmark时获得的数据是从去除换行符到抽象语法树生成完毕，不包括打开文件等操作
		str := *(*string)(unsafe.Pointer(&buf))
		sbuf := strbufpool.Get().(*strings.Reader)
		defer strbufpool.Put(sbuf)
		sbuf.Reset(str)
		r = bufreadpool.Get().(*bufio.Reader)
		defer bufreadpool.Put(r)
		r.Reset(sbuf)
	} else {
		r = bufio.NewReaderSize(file, int(size))
	}
	var t time.Time
	try_new_print_LexTime(&t)
	FT := lex.Lex2(path, r, errctx, false) //进行词法分析
	try_Print_Time(&t)
	try_print_FT(FT)
	if errctx.Errbol() { //如果词法分析有错误·
		return nil, errutil.NewErr2("错误", "词法分析有错")
	}
	try_new_print_AstTime(&t)
	tree := ast.NewTree(FT, errctx, ast.NewSbt(Thread), ImportPackage) //创建抽象语法树
	try_Print_Time(&t)
	try_print(tree)
	err = loadAllImport(filepath.Dir(path), tree.ImportTable, tree.ImportPackage, tree.ImportLoacl, Thread, errctx, tree.Sbt)
	if err != nil {
		return nil, err
	}
	if IsCheck { //如果要进行语义检查
		try_new_print_check(&t)
		check.CheckTree(tree, errctx, Thread)
		try_Print_Time(&t)
	}
	return tree, nil
}
